/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <isa.h>
#include <memory/host.h>
#include <memory/vaddr.h>
#include <device/map.h>

#define IO_SPACE_MAX (2 * 1024 * 1024)

static uint8_t *io_space = NULL;
static uint8_t *p_space = NULL;

uint8_t* new_space(int size) {
  uint8_t *p = p_space;
  // page aligned;
  size = (size + (PAGE_SIZE - 1)) & ~PAGE_MASK;
  p_space += size;
  assert(p_space - io_space < IO_SPACE_MAX);
  return p;
}

static void check_bound(IOMap *map, paddr_t addr) {
  if (map == NULL) {
    Assert(map != NULL, "address (" FMT_PADDR ") is out of bound at pc = " FMT_WORD, addr, cpu.pc);
  } else {
    Assert(addr <= map->high && addr >= map->low,
        "address (" FMT_PADDR ") is out of bound {%s} [" FMT_PADDR ", " FMT_PADDR "] at pc = " FMT_WORD,
        addr, map->name, map->low, map->high, cpu.pc);
  }
}

static void invoke_callback(io_callback_t c, paddr_t offset, int len, bool is_write) {
  if (c != NULL) { c(offset, len, is_write); }
}

void init_map() {
  io_space = malloc(IO_SPACE_MAX);
  assert(io_space);
  p_space = io_space;
}
#define DRING_BUFF_SIZE 128
struct _DRing {
  enum _DType{
    READ,
    WRITE
  } type;
  word_t addr;
  word_t data;
  IOMap *map;
  int len;
} dring_buff[DRING_BUFF_SIZE];
int dring_buff_index = 0;
void dring_buff_save(enum _DType type, word_t addr, word_t data, IOMap *map, int len) {
  dring_buff[dring_buff_index%DRING_BUFF_SIZE].type = type;
  dring_buff[dring_buff_index%DRING_BUFF_SIZE].addr = addr;
  dring_buff[dring_buff_index%DRING_BUFF_SIZE].data = data;
  dring_buff[dring_buff_index%DRING_BUFF_SIZE].map = map;
  dring_buff[dring_buff_index%DRING_BUFF_SIZE].len = len;
  dring_buff_index++;
}

void print_dring_buff(void) {
  printf("dring buff:\n");
  int dev_cnt = dring_buff_index;
  if(dev_cnt > DRING_BUFF_SIZE) dev_cnt = DRING_BUFF_SIZE;
  for (int i = 0; i < dev_cnt; i++)
  {
    struct _DRing s = dring_buff[i];
    if((i+1)%DRING_BUFF_SIZE == dring_buff_index%DRING_BUFF_SIZE) printf("  ==>  ");
    else printf("       ");
    switch (s.type)
    {
    case READ:
      printf("dev:%-8s read  addr: " FMT_WORD " data : " FMT_WORD " len : %d\n",s.map->name,s.addr,s.data,s.len);
      break;
    case WRITE:
      printf("dev:%-8s write addr: " FMT_WORD " data : " FMT_WORD " len : %d\n",s.map->name,s.addr,s.data,s.len);
      break;
    default:
      assert(0);
      break;
    }
  }
  
}
word_t map_read(paddr_t addr, int len, IOMap *map) {
  assert(len >= 1 && len <= 8);
  check_bound(map, addr);
  paddr_t offset = addr - map->low;
  invoke_callback(map->callback, offset, len, false); // prepare data to read
  word_t ret = host_read(map->space + offset, len);
  dring_buff_save(READ, addr, ret, map, len);
  return ret;
}

void map_write(paddr_t addr, int len, word_t data, IOMap *map) {
  assert(len >= 1 && len <= 8);
  check_bound(map, addr);
  paddr_t offset = addr - map->low;
  host_write(map->space + offset, len, data);
  invoke_callback(map->callback, offset, len, true);
  dring_buff_save(WRITE, addr, data, map, len);
}
